十一.虚拟8086模式
继推出80386之后,Intel又推出了80386、Pentium和Pentium PRO。这些处理器都具有
实模式和保护模式两种工作方式。前面已介绍过,实模式与8086兼容,可以运行DOS及
以其为平台的几乎所有软件;但在实模式下,处理器不能发挥自身的优越性能,不能支
持多用户、多任务操作系统的运行。为了充分发挥处理器的功能,同时使DOS及以其为
平台的软件继续有效地运行,从80386开始增加了虚拟8086模式。本文将介绍虚拟8086模
式。
这里下载本文源代码。
<一>V86模式
1.V86模式
虚拟8086模式是保护模式下的一种工作方式,也称为V8086模式,或者简称为V86模式。在
虚拟8086模式下,处理器类似于8086。寻址的地址空间是1M字节;段寄存器的内容作为段
值解释;20位存储单元地址由段值乘以16加偏移构成。在V86模式下,代码段总是可写的,
这与实模式相同,同理,数据段也是可执行的,只不过可能会发生异常。所以,在虚
拟8086模式下,可以运行DOS及以其为平台的软件。但V86模式毕竟是虚拟8086的一种方
式,所以不完全等同于8086。
当标志寄存器中的VM位为1时,处理器就处于V86模式。此时,其当前特权级由处理器自动
设置为3。
2.V86任务
8086程序可以直接在V86模式下运行,而V86模式受到称为V86监控程序的控制。V86监控程
序和在V86模式下的8086程序构成的任务称为虚拟8086任务,或者简称为V86任务。V86任务
形成一个由处理器硬件和属于系统软件的监控程序组成的“虚拟8086机”。V86监控程序控
制V86外部界面、中断和I/O。硬件提供该任务最低端1M字节线性地址空间的虚拟存储空间,
包含虚拟寄存器的TSS,并执行处理这些寄存器和地址空间的指令。
80386把V86任务作为与其它任务具有同等地位的一个任务。它可以支持多个V86任务,每
个V86任务是相对独立的。所以,通过V86模式这种形式,运行8086程序可充分发挥处理器
的能力和充分利用系统资源。
<二>进入和离开V86模式
保护模式和V86模式之间的切换情形如下图所示。图中左面部分为V86任务。从图中可
见,V86模式与保护模式的切换可发生在V86任务之内,这种切换是V86模式下的8086程序
与保护模式下的监控程序之间的转换;V86模式与保护模式的切换可发生在任务之间,这
种切换是V86任务与其它任务的切换。此外,V86监控程序与其它任务之间的切换是普通的
任务切换。
由于80386没有提供直接改变VM标志的指令,并且只有当前特权级CPL=0时,对VM的改变才
有效,所以V86模式与保护模式的切换不能简单地通过改变VM位而进行。下面介绍V86模式
与保护模式之间的切换,也就是如何进入和离开V86模式。为了方便,先介绍如何离开V86模式。
1.离开V86模式
在V86模式下,如果处理器响应中断/异常,那么就会退出当前V86任务的V86模式。
在V86模式下,处理器对中断/异常的响应处理不同于真正的8086,而仍然采用保护模式下对
中断/异常响应处理的方法。所以,在V86模式下,不是根据位于线性地址空间最低端的中断
向量表内的对应中断向量转入处理程序,而是根据中断描述符表IDT内的对应门描述符的指示
转入处理程序。
(1)在V86任务内离开V86模式
如果对应的门描述符是386中断门或386陷阱门,那么就发生在当前V86任务内从V86模式到保
护模式的转换。80386要求执行这种中断/异常处理程序时的CPL必须等于0。
由于V86模式下的CPL=3,而转换到保护模式后的CPL=0,所以这种转换包含了特权级的变换。
在转入处理程序之前,处理器先将V86模式下的段寄存器GS、FS、DS及ES压入0级堆栈,并在
进入保护模式下的处理程序之前装入空选择子。为保持使堆栈对齐,把段寄存器压入堆栈时
一律按32位值压入,低16位是段寄存器的值,高16位为空。于是,转换后的0级堆栈如下图所
示。其中,段寄存器SS和CS的值也是V86模式下的段值。图(a)是没有出错码的情形;图(b)是
有出错码的情形。
在这种V86任务内从V86模式转换到保护模式的过程中,为了保证中断/异常处理程序工作于
特权级0,对目标代码段描述符特权级进行检查,如果由目标代码段描述符特权级决定的CPL不
等于0,将引起通用保护异常。此外,标志寄存器EFLAGS中的VM位被清0,从而使得中断/异常
处理在保护模式下进行,也即离开V86模式。
这种情况下,相应的中断/异常处理在当前V86任务之内进行。中断异常处理程序可以检查保
存在堆栈中的EFLAGS映象,根据VM位的值来确定被中断程序的工作模式。如果VM=1,那么被
中断的程序工作于V86模式,是8086程序;否则,被中断的程序工作于保护模式,是V86监控
程序。
(2)任务切换离开V86模式
如果对应的门描述符是任务门,那么就发生从当前V86任务到其它任务的切换,也就离开了当
前V86任务的V86方式。象普通任务切换一样,V86模式的各通用寄存器、段寄存器、指令指针
和标志寄存器EFLAGS等保存到原V86任务的386TSS中。被保存的段寄存器的内容是V86模式下
的段值。被保存的EFLAGS内的VM=1。
这种情况下,相应的中断异常处理在另一个任务内进行。目标任务可以是普通任务,也可以是
另一个V86任务。如果目标任务TSS内的EFLAGS字段内的VM=1,那么就转入另一个V86任务
的V86模式。
2.进入V86模式
与离开V86模式的两条途径相对应,有两条进入V86模式的途径。
(1)通过IRET指令进入V86模式
通过在中断/异常处理结束时使用IRET指令返回被中断的程序继续执行。指令IRET的执行步骤
如下所示:
(1)若NT=1,则进行任务切换,然后转步骤6;
(2)否则从堆栈中弹出EIP、CS和EFLAGS;
(3)若VM=1且CPL=0,则恢复外层堆栈及其它段寄存器,然后转步骤6;
(4)若无特权级变换则转步骤6;
(5)否则恢复外层堆栈;
(6)结束
尽管上述步骤不够细致和没包括异常情况,但还是体现了指令IRET执行时所处理的三种情形。
第一种情形是当前EFLAGS中的NT=1,也即嵌套任务返回,那么就进行任务切换,指向目标任
务TSS的选择子在当前任务TSS的连接字段。NT=0表示当前中断/异常处理程序与被中断程序属
于同一任务,于是就从堆栈弹出EIP、CS和EFLAGS。第二和第三中情形是NT=0的条件下产生的。
第二种情形是弹出的EFLAGS中VM=0,表示被中断的程序是普通保护模式程序,那么就考虑特
权级变换,如果向外层返回,那么就恢复外层堆栈指针。不允许向内层返回,否则将会引起
通用保护异常。前文中介绍的IRET指令的动作只考虑了情形一和情形二,并不是IRET指令的
完整动作。
第三种情形是弹出的EFLAGS中VM=1且当前正运行程序的CPL=0,表示被中断的程序是V86模式
下的8086程序,当前是从同一V86任务下的中断/异常处理程序返回。由于V86模式的特权级
是3,所以要进行堆栈切换,也即从堆栈中弹出3级堆栈的指针(ESP和SS)。此外,还从堆栈中
弹出段寄存器ES、DS、FS和GS。在这种情形下,弹到各段寄存器(包括CS和SS)的内容都作为
段值,而非选择子。这种处理动作对应于上述第一种离开V86模式的情形,有关堆栈操作与上
图所示的堆栈内容相符。当然,如果产生异常时提供出错码,那么异常处理程序在利用IRET指
令返回时,必须确保堆栈指针指向如上图所示保存EIP的单元。简单的实现方法是,异常处理
程序在执行IRET前,先从堆栈中弹出出错码。
利用指令IRET处理的这第三种情形,可以方便地从V86任务下的中断/异常处理程序返回到V86模
式下的8086程序。利用这条途径还可以直接进入V86模式。为此,先在0级堆栈中形成如上图(a)所
示的栈顶。对应EIP值是V86模式下要执行的8086程序入口点的16位偏移;对应CS值是V86模式下要
执行的8086程序入口点的段值;对应EFLAGS值中的VM位必须是1;对应SS和ESP的值是要执行
的8086程序的堆栈指针;对应ES、DS、FS和GS的值是相应的段值。然后,在CPL=0和NT=0的情况
下,执行IRET指令。实际上,这种进入V86模式的途径是,先建立一个V86模式下执行的8086程序
被中断而离开V86模式的环境,然后再返回。需要注意的是,若当前正执行程序的CPL不为0,
则再执行IRET指令时不会进入V86模式,但也不产生异常,EFLAGS中的VM位被处理器自动清0。
不能通过RET指令进入V86模式,因为它不改变EFLAGS的内容。任何不能修改EFLAGS中VM位的指令
均不能切换到V86模式。
(2)通过任务切换进入V86模式
通过任务切换的途径,可以从其它任务进入V86任务内的V86模式。
利用在前文介绍的任务切换的方法可以进行任务切换。如果目标任务由386TSS描述,并且其
中EFLAGS字段内的VM位为1,那么在切换到目标任务时,也就进入了V86模式。在切换到V86模式
时,CPL被规定为3。目标任务TSS中的各段寄存器字段被解释为8086可以接受的段值,而不是选
择子。任务切换时也将装载LDTR和CR3。
程序在V86模式下执行时,EFLAGS寄存器中的NT位被忽略,所以,不能在V86模式下用IRET指令
完成一个任务切换,并使其工作于V86以外的工作模式。在V86模式下执行IRET指令时,将弹出
IP、CS及FLAGS寄存器,以恢复被中断的程序,而不考虑NT位的值。
如果利用这条途径建立V86任务并进入V86模式,那么主要是把对应386TSS中EFLAGS字段内的VM位
置1,把8086程序的有关段值填入对应386TSS中的相应段寄存器字段。此外,如果V86监控程序需
要用到LDT,那么还要填写LDTR字段;如果需要采用分页机制,那么还要填写CR3字段(当新任务
为V86模式的任务时,只装入段寄存器,而没有装入描述符投影寄存器的动作)。
<三>演示进入和离开V86模式的实例(实例十一)
下面给出一个用于演示进入和离开V86模式的实例。该实例的逻辑功能是,以驻留方式结束程序,
退出时已处于V86模式。该实例演示内容包括:两种方式进入V86模式和两种方式离开V86模式;
V86模式下的8086程序如何调用实模式下的软中断处理程序。
1.演示步骤和源程序清单
为了便于演示,本实例含有三个任务:临时任务,V86任务和INTFF任务。在实模式下做必要的
初始化工作后切换到保护模式,也即进入临时任务,开始演示。演示分两个阶段:第一阶段进
入V86任务的V86模式,并驻留退出;第二阶段进入INTFF任务,切换到临时任务,并返回实模式。
第一阶段的演示步骤如下:
(1)开始临时任务后,作切换到V86任务的准备;
(2)切换到V86任务,由于V86任务TSS中的EFLAGS字段内的VM=1,所以伴随着任务切换就进入
了V86模式。
(3)进入V86任务的V86模式后,显示提示信息,驻留结束,出现DOS提示符,第一阶段至此结束。
在V86模式下,可进行各种操作,运行其它8086程序。如果8086程序引起通用保护异常,那么在
屏幕上显示提示信息,并中止该8086程序。如果在8086程序中执行“INT 0FFH”指令,开始第二
阶段。第二阶段的演示步骤如下:
(1)进入INTFF任务后,显示提示信息,切换到临时任务;
(2)在临时任务内切换到实模式;
(3)在实模式下中止发出“INT 0FFH”的程序
源程序由如下几部分组成:
(1)全局描述符表GDT;
(2)中断描述符表IDT(只适用于V86任务);
(3)INTFF任务的TSS段、LDT段、0级堆栈段和代码段;
(4)V86任务的TSS段、LDT段、0级堆栈段、3级堆栈段及数据段,通用保护异常处理程序
段和其它中断/异常处理程序段,V86模式下的8086程序段;
(5)临时任务的TSS段和代码段;
(6)实模式下的初始化代码段及有关过程。
源程序清单如下:
INCLUDE 386SCD.INC
GDTSeg SEGMENT PARA USE16
GDT LABEL BYTE
DUMMY Desc <>
Normal Desc <0ffffh,,,ATDW,,>
Normal_Sel = Normal-GDT
Video Desc <07fffh,8000h,0bh,ATDW,,>
Video_Sel = Video-GDT
EFFGDT LABEL BYTE
V86TSS Desc <V86TSSLen-1,V86TSSSeg,,AT386TSS,,>
V86TSS_Sel = V86TSS-GDT
V86LDT Desc <V86LDTLen-1,V86LDTSeg,,ATLDT,,>
V86LDT_Sel = V86LDT-GDT
IntFFTSS Desc <IntFFTSSLen-1,IntFFTSSSeg,,AT386TSS,,>
IntFFTSS_Sel = IntFFTSS-GDT
IntFFLDT Desc <IntFFLDTLen-1,IntFFLDTSeg,,ATLDT,,>
IntFFLDT_Sel = IntFFLDT-GDT
TempTSS Desc <TempTSSLen-1,TempTSSSeg,,AT386TSS,,>
TempTSS_Sel = TempTSS-GDT
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel = TempCode-GDT
GDNum = ($-EFFGDT)/(SIZE Desc)
GDTLen = $-GDT
GDTSeg ENDS
IDTSeg SEGMENT PARA USE16
IDT LABEL BYTE
REPT 13
Gate <,TPCode_Sel,,AT386IGate+DPL3,>
ENDM
Gate <GPBegin,GPCode_Sel,,AT386TGate+DPL3,>
REPT 241
Gate <,TPCode_Sel,,AT386IGate+DPL3,>
ENDM
Gate <,IntFFTSS_Sel,,ATTaskGate+DPL3,>
IDTLen = $-IDT
IDTSeg ENDS
IntFFTSSSeg SEGMENT PARA USE16
DD 0
DD 0
DW 0,0
DD 0
DW 0,0
DD 0
DW 0,0
DD 0
DW IntFFBegin,0
DD 0
DD 0
DD 0
DD 0
DD 0
DD IntFFStackLen
DD 0
DD 0
DD 0
DW Normal_Sel,0
DW IntFFCode_Sel,0
DW IntFFStack_Sel,0
DW Normal_Sel,0
DW Normal_Sel,0
DW Normal_Sel,0
DW IntFFLDT_Sel,0
DW 0
DW $+2
DB 0ffh
IntFFTSSLen = $
IntFFTSSSeg ENDS
IntFFLDTSeg SEGMENT PARA USE16
FLDT LABEL BYTE
IntFFStack Desc <IntFFStackLen-1,IntFFStackSeg,,ATDWA,,>
IntFFStack_Sel = IntFFStack-FLDT+TIL
IntFFCode Desc <IntFFCodeLen-1,IntFFCodeSeg,,ATCER,,>
IntFFCode_Sel = IntFFCode-FLDT+TIL
IntFFLDNum = ($-FLDT)/(SIZE Desc)
IntFFLDTLen = $
IntFFLDTSeg ENDS
IntFFStackSeg SEGMENT PARA USE16
IntFFStackLen = 512
DB IntFFStackLen DUP(0)
IntFFStackSeg ENDS
IntFFCodeSeg SEGMENT PARA USE16
ASSUME CS:IntFFCodeSeg
IntFFMess DB 'Return to real mode.'
IntFFMessLen = $-IntFFMess
IntFFBegin PROC FAR
mov si,OFFSET IntFFMess
mov ax,Video_Sel
mov es,ax
mov di,0
mov ah,17h
mov cx,IntFFMessLen
cld
INext: mov al,BYTE PTR cs:[si]
inc si
stosw
loop INext
JUMP16 TempTSS_Sel,0
IntFFBegin ENDP
IntFFCodeLen = $
IntFFCodeSeg ENDS
V86TSSSeg SEGMENT PARA USE16
DD 0
DD V86Stack0Len
DW V86Stack0_Sel,0
DD 0
DW 0,0
DD 0
DW 0,0
DD 0
DW V86Begin,0
DD IOPL3 OR VMFL
DD 0
DD 0
DD 0
DD 0
DD V86Stack3Len
DD 0
DD 0
DD 0
DW V86CodeSeg,0
DW V86CodeSeg,0
DW V86Stack3Seg,0
DW V86CodeSeg,0
DW V86CodeSeg,0
DW V86CodeSeg,0
DW V86LDT_Sel,0
DW 0
DW $+2
DB 4000h/8 DUP(0)
DB 0ffh
V86TSSLen = $
V86TSSSeg ENDS
V86LDTSeg SEGMENT PARA USE16
VLDT LABEL BYTE
VAllMem Desc <0ffffh,,,ATDWA,0fh,>
VAllMem_Sel = VAllMem-VLDT+TIL
V86Stack0 Desc <V86Stack0Len-1,V86Stack0Seg,,ATDWA,,>
V86Stack0_Sel = V86Stack0-VLDT+TIL
V86Data Desc <V86DataLen-1,V86DataSeg,,ATDR,,>
V86Data_Sel = V86Data-VLDT+TIL
TPCode Desc <TPCodeLen-1,TPCodeSeg,,ATCE,,>
TPCode_Sel = TPCode-VLDT+TIL
GPCode Desc <GPCodeLen-1,GPCodeSeg,,ATCE,,>
GPCode_Sel = GPCode-VLDT+TIL
V86LDNum = ($-VLDT)/(SIZE Desc)
V86LDTLen = $
V86LDTSeg ENDS
V86Stack0Seg SEGMENT PARA USE16
V86Stack0Len = 512
DB V86Stack0Len DUP(0)
V86Stack0Seg ENDS
V86Stack3Seg SEGMENT PARA USE16
V86Stack3Len = 1024
DB V86Stack3Len DUP(0)
V86Stack3Seg ENDS
V86DataSeg SEGMENT PARA USE16
GPErrMess DB '......General Protection Error......'
GPErrMessLen = $-GPErrMess
V86DataLen = $
V86DataSeg ENDS
Perr EQU <WORD PTR [BP+0]>
Pip EQU <WORD PTR [bp+4]>
Pcs EQU <WORD PTR [bp+8]>
Pflag EQU <WORD PTR [bp+12]>
Psp EQU <WORD PTR [bp+16]>
Pss EQU <WORD PTR [bp+20]>
Pes EQU <WORD PTR [bp+24]>
Pds EQU <WORD PTR [bp+28]>
Pfs EQU <WORD PTR [bp+32]>
Pgs EQU <WORD PTR [bp+36]>
TPCodeSeg SEGMENT PARA USE16
ASSUME CS:TPCodeSeg
TPBegin PROC FAR
Count = 0
REPT 256
IF Count EQ 21h
Ent21H LABEL BYTE
ENDIF
push bp
mov bp,Count
jmp Process
Count = Count+1
ENDM
Process: push bp
mov bp,sp
push eax
push ebx
mov ax,VAllMem_Sel
mov ds,ax
xor eax,eax
mov ax,Psp
sub ax,3*2
mov Psp,ax
xor ebx,ebx
mov bx,Pss
shl ebx,4
add ebx,eax
mov ax,Pip
mov WORD PTR [ebx],ax
mov ax,Pcs
mov WORD PTR [ebx+2],ax
mov ax,Pflag
mov WORD PTR [ebx+4],ax
mov bx,[bp]
shl bx,2
mov ax,[bx]
mov Pip,ax
mov ax,[bx+2]
mov Pcs,ax
pop ebx
pop eax
pop bp
pop bp
iretd
TPBegin ENDP
TPCodeLen = $
TPCodeSeg ENDS
GPCodeSeg SEGMENT PARA USE32
ASSUME CS:GPCodeSeg
GPBegin PROC FAR
mov ax,V86Data_Sel
mov ds,ax
mov si,OFFSET GPErrMess
mov ax,Video_Sel
mov es,ax
mov di,0
mov ah,17h
mov cx,GPErrMessLen
cld
GNext: lodsb
stosw
loop GNext
add esp,4
mov ax,4c01h
JUMP16 TPCode_Sel,Ent21H
GPBegin ENDP
GPCodeLen = $
GPCodeSeg ENDS
V86CodeSeg SEGMENT PARA USE16
ASSUME CS:V86CodeSeg,DS:V86CodeSeg
Message DB 'V86 is OK!',0dh,0ah,24h
V86Begin PROC FAR
mov ah,9
mov dx,OFFSET Message
int 21h
mov ax,RCodeSeg
sub ax,GDTSeg
mov dx,OFFSET TSRLine+15
shr dx,4
add dx,ax
add dx,10h
mov ax,3100h
int 21h
V86Begin ENDP
V86CodeSeg ENDS
TempTSSSeg SEGMENT PARA USE16
TSS <>
DB 0ffh
TempTSSLen = $
TempTSSSeg ENDS
TempCodeSeg SEGMENT PARA USE16
ASSUME CS:TempCodeSeg
Virtual PROC FAR
mov ax,Normal_Sel
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov ax,TempTSS_Sel
ltr ax
JUMP16 V86TSS_Sel,0
ToDos: clts
mov eax,cr0
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
Virtual ENDP
TempCodeSeg ENDS
RDataSeg SEGMENT PARA USE16
RDataSeg ENDS
RCodeSeg SEGMENT PARA USE16
ASSUME CS:RCodeSeg,DS:RCodeSeg
VGDTR PDesc <GDTLen-1,>
VIDTR PDesc <IDTLen-1,>
NORVIDTR PDesc <>
SPVar DW ?
SSVar DW ?
Start PROC
mov ax,RCodeSeg
mov ds,ax
cld
call InitGDT
call InitIDT
mov ax,V86LDTSeg
mov fs,ax
mov cx,V86LDNum
mov si,OFFSET VLDT
call InitLDT
mov ax,IntFFLDTSeg
mov fs,ax
mov cx,IntFFLDNum
mov si,OFFSET FLDT
call InitLDT
mov SSVar,ss
mov SPVar,sp
lgdt QWORD PTR VGDTR
sidt QWORD PTR NORVIDTR
cli
lidt QWORD PTR VIDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <TempCode_Sel>,<OFFSET Virtual>
Real: mov ax,cs
mov ds,ax
lss sp,DWORD PTR SPVar
lidt QWORD PTR NORVIDTR
sti
mov ax,4c00h
int 21h
Start ENDP
TSRLine LABEL BYTE
InitGDT PROC
push ds
mov ax,GDTSeg
mov ds,ax
mov cx,GDNum
mov si,OFFSET EFFGDT
InitG: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR [si].BaseL,ax
mov BYTE PTR [si].BaseM,dl
mov BYTE PTR [si].BaseH,dh
add si,SIZE Desc
loop InitG
pop ds
mov bx,16
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
ret
InitGDT ENDP
InitLDT PROC
mov ax,WORD PTR FS:[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR fs:[si].BaseL,ax
mov BYTE PTR fs:[si].BaseM,dl
mov BYTE PTR fs:[si].BaseH,dh
add si,SIZE Desc
loop InitLDT
ret
InitLDT ENDP
InitIDT PROC
push ds
mov ax,IDTSeg
mov ds,ax
mov cx,256-1
mov si,OFFSET IDT
mov ax,OFFSET TPBegin
IIDT1: cmp cx,256-1-13
jz IIDT2
mov [si],ax
IIDT2: add si,8
add ax,7
loop IIDT1
pop ds
mov bx,16
mov ax,IDTSeg
mul bx
mov WORD PTR VIDTR.Base,ax
mov WORD PTR VIDTR.Base+2,dx
ret
InitIDT ENDP
RCodeSeg ENDS
END Start
用于演示INTFF任务的源程序如下:
Text SEGMENT
ASSUME cs:Text,ds:Text
Start PROC
int 0ffh
mov ax,4c00h
int 21h
Start ENDP
Text ENDS
END Start
2.说明
(1)对IDT表的初始化
为了方便地书写IDT表,采用了重复汇编。从采用重复汇编方式定义的IDT表可见,除对应通用
保护异常的13号陷阱门描述符和255号任务门描述符外,其它中断门描述符内的偏移均未设定。
为此,在实模式下初始化时,把相应的入口偏移填入这些门描述符。从源程序可见,这些处理
程序的入口偏移也用重复汇编书写,并且字节数相同,所以间隔等长。
(2)任务切换方式进入V86模式
实例从临时任务切换到V86任务时进入V86模式。在V86任务的TSS中,EFLAGS字段内的VM=1,所
以伴随着任务切换,就进入了V86模式。为此,TSS中对应各段寄存器字段内的初始值都是V86模
式下要执行的8086程序各段的段值,而非选择子。由于在发生中断/异常时要进入V86任务的特
权级0,所以初始化了0级堆栈指针。V86任务使用局部描述符表LDT,所以TSS中对应字段填有相
应的选择子。
(3)V86模式下对中断的处理
实例对V86模式下响应中断和执行软中断指令“INT n”的处理方法是,转实模式下的对应中断
处理程序。具体步骤如下:
1)在V86模式堆栈(V86任务的3级堆栈)顶形成返回点的现场;
2)用实模式下对应的中断向量值代替返回地址;
3)从保护模式返回V86模式。由于堆栈中保存的EFLAGS内的VM=1,所以在保护模式下执
行IRET指令时,返回V86模式;由于在0级堆栈中保存的返回地址(段值和偏移)已被修改成实模
式下的中断向量,所以这时的返回也就是转入实模式下的对应中断处理程序;由于在V86堆栈顶
已安排了返回地址,所以在执行实模式下对应中断处理程序时,遇到IRET指令就返回到V86任务
的被中断处。这种处理方法似乎绕了个弯。但就是利用这个方法有效地调用了DOS功能,方便地
显示了提示信息“V86 is OK.”,顺利地实现了驻留退出。
这种处理方法没有充分考虑异常,更没有考虑异常时的出错码。
(4)V86任务的通用保护异常处理
在演示的第一阶段和第二阶段,不会发生任何异常。但在这两个阶段之间,由于允许执行其它
程序,可能引起异常。为了简单,实例只考虑了通用保护异常,而未考虑其它异常。该可能发
生异常的阶段是在V86模式下。如果发生通用保护异常,那么就转入V86任务的通用保护异常处
理程序。通用保护异常处理程序在保护模式下显示提示信息
“......General Protection Error......”,然后再转21H号中断处理程序,通过设置入口
参数AX=4C01H,中止引起通用保护异常的程序。
(5)INTFF任务
为了演示以任务切换方式离开V86模式,在实例中安排了这一任务,并称之为INTFF任务。由于
中断描述符表IDT中的255(0FFH)号描述符是任务门描述符,所以当在V86模式下执行软中断指
令“INT 0FFH”时便从V86模式切换到INTFF任务。INTFF任务的TSS是初始化好的,在INTFF任
务下不发生特权级变换,不使用局部描述符表LDT。INTFF任务先显示提示信息
“Return to real mode.”,然后再切换到临时任务。
<四>V86模式下的敏感指令
在V86模式下,CPL=3,执行特权指令时,或者要引起出错码为0的通用保护故障,或者要引起
非法操作码故障。
在V86模式下,输入/输出指令的敏感条件有所变化。指令CLI和STI的敏感条件不变,由于CPL=3,
所以如果IOPL<3,那么执行CLI或STI指令将引起通用保护故障。输入/输出指令IN、INS、OUT或OUTS的
敏感条件仅仅是当前V86任务TSS内的I/O许可位图,而忽略EFLAGS中的IOPL。输入/输出指
令IN、INS、OUT或OUTS是否可以执行与CPL是否小于IOPL无关,而直接由I/O许可位图对应位决
定,如果输入/输出指令所使用I/O地址对应的I/O许可位图内的各位都位0,则输入输出指令可
正常执行,否则引起通用保护故障。
此外,在V86模式下,指令PUSHF、POPF、INT n和IRET却对IOPL敏感。也就是说,在V86模式下,
当IOPL<3时,执行指令PUSHF、POPF、INT n及IRET会引起出错码为0的通用保护故障。
采取上述措施的目的是使操作系统软件可以支持一个“虚拟EFLAGS”寄存器。
参考资料 |
书 名 |
出 版 社 |
作 者 |
《保护方式下的80386及其编程》 |
清华大学出版社 |
周明德主编 |
《80X86汇编语言程序设计教程》 |
清华大学出版社 |
扬季文主编 |